﻿using Flame.Reflection;
using System;
using System.Collections.Generic;
using System.Text;

namespace Flame.Dlr
{
    public interface IStreamedOutput
    {
        void Set(System.IO.Stream stream);
    }

    public class ExecutableWithEvent : IExecutable
    {
        IExecutable _exec;

        public event EventHandler<InputEventArg> OnInput;
        public event EventHandler<StreamOutput.StreamOutputEventArg> OnOutput;
        public event EventHandler<ResultEventArg> OnResult;
        public event EventHandler<ErrorEventArg> OnError;
        public event EventHandler<ClearedScopeEventArgs> OnClearedScope;

        public ExecutableWithEvent(IExecutable exec)
        {
            _exec = exec;
            if (exec is IStreamedOutput)
            {
                var streamInOut = new StreamOutput();
                streamInOut.OnOutput += (sender, e) =>
                {
                    if (OnOutput != null)
                    {
                        OnOutput(sender, e);
                    }
                };
                (exec as IStreamedOutput).Set(streamInOut);
            }
        }

        string onInput(string script)
        {
            if (OnInput != null)
            {
                var e = new InputEventArg() { Input = script, Language = Language };
                OnInput(this, e);
                return e.Input;
            }
            return script;
        }

        bool onError(Exception ex)
        {
            if (OnError != null)
            {
                var e = new ErrorEventArg() { Error = ex, Language = Language };
                OnError(this, e);
                return !e.ReTry;
            }
            return true;
        }

        Result onResult(Result result)
        {
            if (OnResult != null)
            {
                var e = new ResultEventArg() { Result = result, Language = Language };
                OnResult(this, e);
                return e.Result;
            }
            return result;
        }
        
        public Result Execute(string script)
        {
            var result = new Result();

            bool interrupt = false;

            while (!interrupt)
            {
                try
                {
                    result = onResult(_exec.Execute(onInput(script)));
                    interrupt = true;
                }
                catch (Exception e)
                {
                    interrupt = onError(e);
                    result.Data = e;
                }
            }
            return result;
        }

        public ResultAssembly AddAssembly(AssemblyWrapper assembly)
        {
            bool interrupt = false;
            dynamic d = null;
            Exception e_ = null;
            while (!interrupt)
            {
                try
                {

                    d = _exec.AddAssembly(assembly);
                    interrupt = true;
                }
                catch (Exception e)
                {
                    e_ = e;
                    interrupt = onError(e);
                }
            }

            return new ResultAssembly() { Loaded = e_ == null, Exception = e_, Language = Language };
        }

        public Type Language { get { return _exec.GetType(); } }

        public void AddVariable(Variable variable) { _exec.AddVariable(variable); }
        public IIntellisense Intellisense { get { return _exec.Intellisense; } }

        public virtual void ClearScope(ScopeParameters scopeParameters)
        {
            _exec.ClearScope(scopeParameters);
            if (OnClearedScope != null)
            {
                OnClearedScope(this, new ClearedScopeEventArgs() { Exec = _exec });
            }
        }
    }
}
